package main

import (
	"bytes"
	"crypto/btcutil/base58"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/ripemd160"
	"crypto/sha256"
	"encoding/binary"
	"encoding/gob"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math/big"
	"os"
	"strconv"
	"strings"
	"test/bolt"
	"time"
)

type Block struct {
	//1、版本号
	Version uint64
	//2、梅克尔根，这就是一个哈希
	Merkel []byte
	//3、时间戳
	BcTime uint64
	//4、随机数，也就是挖矿要找的数据
	Nonce uint64
	//5、难度值
	Diffcult uint64
	//6、前区块hash
	PreHash []byte
	//7、交易数组
	Transactions []*Transaction
	//8、当前区块的哈希
	MyHash []byte
}
type ProofOfWork struct {
	//block
	block *Block
	// 目标值：target，这个目标值要与生成哈希值比较
	target *big.Int //结构，提供了方法：比较，把哈希值设置为big.Int类型
}

//block由用户提供
//target目标值由系统提供
func NewProof(block *Block) *ProofOfWork {
	pow := ProofOfWork{
		block: block,
	}
	pow.block = block
	//难度值先写死，不去推导，后面补充推导方式
	targetStr := "0f00000000000000000000000000000000000000000000000000000000000000"
	tmpBigInt := new(big.Int)
	//将我们的难度值赋值给bigint，是16进制，4位可以确定一个16进制数，所以我们需要64个16进制数，这样正好256位
	tmpBigInt.SetString(targetStr, 16)
	pow.target = tmpBigInt
	return &pow
}

//1、构建区块
func NewBlock(txs []*Transaction, preHash []byte) *Block {
	block := Block{
		Version:      00,
		Merkel:       []byte{},
		BcTime:       uint64(time.Now().Unix()),
		Nonce:        00,
		Diffcult:     20,
		PreHash:      preHash,
		Transactions: txs,
		MyHash:       []byte{},
	}
	block.Merkel = block.NewMerkelRoot()
	//block.SetHash()
	pow := NewProof(&block)
	//不断进行hash运算
	myhash, num := pow.Run()
	block.Nonce = num
	block.MyHash = myhash
	return &block
}

//简单的转换函数
func Uint64toByte(num uint64) []byte {
	var buffer bytes.Buffer
	err := binary.Write(&buffer, binary.BigEndian, num)
	if err != nil {
		panic(err)
	}
	return buffer.Bytes()
}

//2、生成一个hash
func (block *Block) SetHash() {
	//var blockInfo []byte
	//拼接数据
	/*
		blockInfo = append(blockInfo,block.PreHash...)
		blockInfo = append(blockInfo,Uint64toByte(block.Version)...)
		blockInfo = append(blockInfo,Uint64toByte(block.Diffcult)...)
		blockInfo = append(blockInfo,Uint64toByte(block.Nonce)...)
		blockInfo = append(blockInfo,Uint64toByte(block.BcTime)...)
		blockInfo = append(blockInfo,Uint64toByte(block.Merkel)...)
	*/
	temp := [][]byte{
		block.PreHash,
		//block.Data,
		Uint64toByte(block.Version),
		Uint64toByte(block.Diffcult),
		Uint64toByte(block.Nonce),
		Uint64toByte(block.BcTime),
		block.Merkel,
	}
	//使用join方法，将二维切片转为1维切片
	blockInfo := bytes.Join(temp, []byte{})
	//把他们生成hash
	hash := sha256.Sum256(blockInfo)
	block.MyHash = hash[:]
}

type BlockChain struct {
	//创建一个区块数组
	//block []*Block
	//记录最后一个区块的hash
	db   *bolt.DB
	tail []byte
}

const blockChainDB = "blockChain.db"
const blockBucket = "blockBucket"
const walletFile = "wallet.dat"

//定义一个区块链
func NewBlockChain(address string) *BlockChain {
	//并且先添加一个创世块
	//genesblock := GenesisBlock()

	//return &BlockChain{
	//	[]*Block{genesblock},
	//}
	//定义最后一个区块
	var lastHash []byte

	db, err := bolt.Open(blockChainDB, 0600, nil)
	if err != nil {
		log.Panic("打开数据库失败")
	}

	err = db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			//log.Panic("没有bucket")
			bucket, err = tx.CreateBucket([]byte(blockBucket))
			if err != nil {
				log.Panic("创建失败")
			}
			genesisBlock := GenesisBlock(address)
			bucket.Put(genesisBlock.MyHash, genesisBlock.Serialize())
			bucket.Put([]byte("LastHash"), genesisBlock.MyHash)

			//测试反序列化
			//test := Deserialization(genesisBlock.Serialize())
			//fmt.Println("序列化前：",genesisBlock)
			//fmt.Println("序列化后:",genesisBlock.Serialize())
			//fmt.Printf("反序列化后：%s",test)
			lastHash = bucket.Get([]byte("LastHash"))
		}
		return nil
	})
	return &BlockChain{db: db, tail: lastHash}
}

func (bc *BlockChain) FindTransactionByTXid(id []byte) (Transaction, error) {
	//1、遍历区块链
	//2、遍历交易
	//3、比较交易，找到了直接退出
	//4、如果没找到，返回空Transaction,同时返回错误
	it := bc.NewIterator()
	for {
		block := it.Next()
		for _, tx := range block.Transactions {
			if bytes.Equal(tx.TXID, id) {
				return *tx, nil
			}
		}
		if len(block.PreHash) == 0 {
			fmt.Printf("区块链遍历结束！\n")
			break
		}
	}
	return Transaction{}, errors.New("无效的交易id，请检查！")
}

func (bc *BlockChain) SignTransaction(tx *Transaction, key *ecdsa.PrivateKey) {
	//签名，交易创建的最后进行签名
	prevTXs := make(map[string]Transaction)

	//找到所有引用的交易
	//1、根据inputs来找，有多少inputs，就遍历多少次
	//2、找到目标交易，{根据TXid来找}
	//3、添加到prevTXs里面
	for _, input := range tx.TXInputs {
		tx, err := bc.FindTransactionByTXid(input.TXid)
		if err != nil {
			log.Panic("FindTransactionByTXid ：", err)
		}

		prevTXs[string(input.TXid)] = tx

		//根据id查找交易本身，需要遍历整个区块链
		//第一个input查找之后，prevTXs：
		//map[2222]Transaction222

		//第二个input查找之后，prevTXs：
		//map[3333]Transaction333
	}

	tx.Sign(key, prevTXs)
}

func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool {

	if tx.IsCoinbase() {
		return true
	}
	//判断交易是否为空，bug
	if tx == nil {
		return false
	}

	//签名，交易创建的最后进行签名
	prevTXs := make(map[string]Transaction)

	//找到所有引用的交易
	//1、根据inputs来找，有多少inputs，就遍历多少次
	//2、找到目标交易，{根据TXid来找}
	//3、添加到prevTXs里面
	for _, input := range tx.TXInputs {
		tx, err := bc.FindTransactionByTXid(input.TXid)
		if err != nil {
			log.Panic("FindTransactionByTXid ：", err)
		}
		prevTXs[string(input.TXid)] = tx
	}
	return tx.Verify(prevTXs)
}

//区块转化为数据流
//序列化
func (block *Block) Serialize() []byte {

	var buf bytes.Buffer
	gobEn := gob.NewEncoder(&buf)
	//	2）编码器编码
	err := gobEn.Encode(&block)
	if err != nil {
		fmt.Println("编码错误：", err)
	}
	return buf.Bytes()
}

//反序列化
func Deserialization(data []byte) Block {
	gobDe := gob.NewDecoder(bytes.NewReader(data))
	var dm Block
	//	2）解码器解码
	err := gobDe.Decode(&dm)
	if err != nil {
		fmt.Println("反序列错误：", err)
	}
	return dm
}

//定义一个创世块（作为第一个）
func GenesisBlock(address string) *Block {
	coinbase := NewCoinBaseTx(address, "我就是创世！")
	return NewBlock([]*Transaction{coinbase}, []byte{})
}

//添加区块
func (blockchain *BlockChain) AddBlock(txc []*Transaction) {

	for _, tx := range txc {
		if !blockchain.VerifyTransaction(tx) {
			fmt.Printf("矿工发现交易无效！")
			return
		}
	}

	//打开数据库
	db := blockchain.db

	//添加数据
	db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("没有数据库，无法添加！")
		}
		block := NewBlock(txc, blockchain.tail)
		bucket.Put(block.MyHash, block.Serialize())
		bucket.Put([]byte("LastHash"), block.MyHash)

		//更新内存中的尾巴————最后一个区块的hash
		blockchain.tail = block.MyHash
		return nil
	})
}

//挖矿
func (pow *ProofOfWork) Run() ([]byte, uint64) {
	//1、拼装数据(区块数据和不断变化的随机数)
	var nonce uint64
	var hash [32]byte
	block := pow.block
	for {
		temp := [][]byte{
			block.PreHash,
			//只对区块头做哈希值，区块体通过梅克尔根影响
			//block.Data,
			Uint64toByte(block.Version),
			Uint64toByte(block.Diffcult),
			Uint64toByte(block.Nonce),
			Uint64toByte(block.BcTime),
			Uint64toByte(nonce), //随机数
			block.Merkel,
		}
		//使用join方法，将二维切片转为1维切片
		blockInfo := bytes.Join(temp, []byte{})
		//2、做哈希运算
		hash = sha256.Sum256(blockInfo)
		//3、比较hash值
		tartmp := new(big.Int)
		tartmp.SetBytes(hash[:])
		//比较
		//   -1 if x <  y
		//    0 if x == y
		//   +1 if x >  y
		//当前计算的哈希.Cmp(难度值)
		if tartmp.Cmp(pow.target) == -1 {
			fmt.Printf("挖矿成功,hash :%x, nonce :%d\n", hash[:], nonce)
			break
		} else {
			nonce++
		}
	}

	return hash[:], nonce
}

//迭代器结构体
type BlockChainIterator struct {
	BchainItor *BlockChain
	//设置一个hash游标,当前区块的hash
	TmpHash []byte
}

//新建迭代器
func (bc *BlockChain) NewIterator() *BlockChainIterator {
	return &BlockChainIterator{
		BchainItor: bc,
		//从尾部往前面遍历，所以先获取尾部的hash
		TmpHash: bc.tail,
	}
}

//迭代器遍历
func (bci *BlockChainIterator) Next() *Block {
	var block Block
	bci.BchainItor.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("没有数据，请在数据库添加数据！")
		}
		tmpBy := bucket.Get(bci.TmpHash)
		block = Deserialization(tmpBy)
		bci.TmpHash = block.PreHash
		return nil
	})
	return &block
}

//使用命令行的模式创建区块链
type Cli struct {
	bc *BlockChain
}

const CliName = `
	addblock	--data Data	"add block to blockchain"
	printblock				"print all blockchain data"
	getBalance --address ADDRESS "获取指定地址的余额"
	send FROM TO AMOUNT MINER DATA "由FROM转AMOUNT给TO，由MINER挖矿，同时写入DATA"
	newWallet "NEW A WALLET"
	listAlladdress	列出所有地址
`

func (c *Cli) Run() {
	//得到所有命令
	cli := os.Args
	if len(cli) < 2 {
		fmt.Println(CliName)
		return
	}
	//获取命令
	cmd := cli[1]
	switch cmd {
	case "addblock":
		fmt.Println("add")
		if len(cli) == 4 && cli[2] == "--data" {
			c.AddBlock(cli[3])
		} else {
			fmt.Println("输入类型错误，需为strings！")
			fmt.Printf(CliName)
		}
	case "printblock":
		fmt.Println("print")
		c.bc.PrintBlockchain()
	case "getBalance":
		fmt.Printf("获取余额\n")
		if len(cli) == 4 && cli[2] == "getBalance" {
			address := cli[3]
			c.GetBalance(address)
		}
	case "send":
		fmt.Printf("转账开始。。。\n")
		if len(cli) != 7 {
			fmt.Printf("参数个数错误，请检查！\n")
			fmt.Printf(CliName)
			return
		}
		from := cli[2]
		to := cli[3]
		amount, _ := strconv.ParseFloat(cli[4], 64) //string转float
		miner := cli[5]
		data := cli[6]
		c.Send(from, to, amount, miner, data)
	case "newWallet":
		fmt.Printf("创建一个钱包！\n")
		c.NewWallet()
	case "listAlladdress":
		fmt.Printf("列出所有地址！\n")
		c.ListAllAddress()
	default:
		fmt.Println("无效的命令!")
		fmt.Println(CliName)
	}
}

func (c *Cli) AddBlock(data string) {
	//TODO
	fmt.Printf("添加区块成功！\n")
}

func (bc *BlockChain) PrintBlockchain() {
	it := bc.NewIterator()
	for {
		block := it.Next()

		for _, tx := range block.Transactions {
			tx.ToString()
		}

		//fmt.Printf("======================================\n")
		//fmt.Printf("---Block Prehash:%x\n", block.PreHash)
		//fmt.Printf("---Block Data:	%s\n", block.Transactions[0].TXInputs[0].PubKey)
		//fmt.Printf("梅克尔根 ：%v\n", block.Merkel)
		//timeFormat := time.Unix(int64(block.BcTime), 0).Format("2006-06-02 15:04:05")
		//fmt.Printf("时间戳：%s\n", timeFormat)
		//fmt.Printf("---Block Myhash:	%x\n", block.MyHash)
		if len(block.PreHash) == 0 {
			fmt.Println("遍历结束！")
			break
		}
	}
}

//打印
func (tx Transaction) ToString() string {
	var lines []string
	lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TXID))

	for i, input := range tx.TXInputs {
		lines = append(lines, fmt.Sprintf("   Input: %d", i))
		lines = append(lines, fmt.Sprintf("       TXID:  %x", input.TXid))
		lines = append(lines, fmt.Sprintf("       Out:   %d", input.Index))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
		lines = append(lines, fmt.Sprintf("       PubKey: %x", input.PubKey))
	}

	for i, output := range tx.TXOutputs {
		lines = append(lines, fmt.Sprintf("   Output: %d", i))
		lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
		lines = append(lines, fmt.Sprintf("       Sctrpt: %x", output.PubKeyHash))
	}

	return strings.Join(lines, "\n")
}

const reward = 12.5

//1、定义交易结构
type Transaction struct {
	TXID      []byte     //交易ID
	TXInputs  []TXInput  //交易数组
	TXOutputs []TXOutput //交易输出的数组
}

//2、定义交易输入
type TXInput struct {
	TXid      []byte //交易ID
	Index     int64  //引用的output的索引值
	Signature []byte //真正的数字签名，由r，s拼成的[]byte
	//约定这里的PubKey不存储原始的公钥，而是存储X和Y拼接的字符串，在校验端重新拆分（参考r，s传递）
	PubKey []byte
}

//3、定义交易输出
type TXOutput struct {
	Value float64 //转账金额

	//收款方的公钥的哈希，注意，是哈希而不是公钥，也不是地址
	PubKeyHash []byte //锁定脚本，我们用地址模拟
}

//由于现在存储的字段是地址的公钥哈希，所以直接创建TXOutput，
//为了能够得到公钥哈希，我们需要处理一下，写一个Lock方法
func (output *TXOutput) Lock(address string) {

	//真正的锁定动作！！！
	output.PubKeyHash = GetPubKeyFromAddress(address)

}

//设置交易ID
func (tx *Transaction) SetHash() {
	var buffer bytes.Buffer
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(tx)
	if err != nil {
		log.Panic(err)
	}
	data := buffer.Bytes()
	hash := sha256.Sum256(data)
	tx.TXID = hash[:]
}

func NewTxOutput(rw float64, address string) *TXOutput {
	output := TXOutput{
		Value: rw,
	}
	output.Lock(address) //设置公钥hash
	return &output
}

func NewCoinBaseTx(address string, data string) *Transaction {
	//挖矿交易的特点：
	//	只有一个input
	//	无需引用交易ID
	//	无需引用index
	//	矿工由于挖矿时无需指定签名，所以这个sig字段可以由矿工自由填写数据，一般是填写矿池的名字
	//签名先填写为空，后面创建完成交易后，再进行一次签名。
	input := TXInput{[]byte{}, -1, nil, []byte(data)}
	output := NewTxOutput(reward, address)

	//对于挖矿交易来说，只有一个input和一个output
	tx := Transaction{[]byte{}, []TXInput{input}, []TXOutput{*output}}
	tx.SetHash()
	return &tx
}

//生成梅克尔根（哈希值）
func (block *Block) NewMerkelRoot() []byte {
	var info []byte
	//var finalInfo [][]byte
	for _, tx := range block.Transactions {
		//将交易的哈希值拼接起来，再整体做哈希处理
		info = append(info, tx.TXID...)
	}
	hash := sha256.Sum256(info)
	return hash[:]
}

//查找指定的address的UTXO
func (bc *BlockChain) FindUtxo(address []byte) []TXOutput {
	var utxos []TXOutput
	spentOutputs := make(map[string][]int64)

	//创建迭代器
	it := bc.NewIterator()

	for {
		//1、遍历区块
		block := it.Next()
		//遍历交易
		for _, tx := range block.Transactions {
			fmt.Printf("current txid: %x\n", tx.TXID)
			//3、遍历output，找到和自己相关的utxo（在添加output之前检查一下是否已经消耗过）
		output:
			for i, output := range tx.TXOutputs {
				fmt.Printf("current index: %d\n", i)
				//在这里做一个过滤，将所有消耗过得output和当前得所有即将添加output对比一下
				//如果相同，则跳过，否则添加
				//如果当前得交易id存在于我们表示的map，那么说明这个交易里面有消耗过的output

				if spentOutputs[string(tx.TXID)] != nil {
					for _, j := range spentOutputs[string(tx.TXID)] {
						if int64(i) == j {
							//当前准备添加output已经消耗过了，不要再加了
							continue output
						}
					}
				}

				//这个output和我们目标的地址相同，满足条件，加到返回UTXO数组中。
				if bytes.Equal(address, output.PubKeyHash) {
					utxos = append(utxos, output)
					fmt.Printf("333333:%f\n", utxos[0].Value)
				} else {
					fmt.Printf("33333")
				}
			}
			//如果是挖矿交易，直接跳过
			if tx.IsCoinbase() {
				//4、遍历input，找到自己花费过的utxo的集合（把自己的消耗过的标示出来）
				//我们定义一个map来保存消费过的output，key是这个output的交易id，value是
				for _, input := range tx.TXInputs {
					//判断一下当前这个input和目标是否一致，如果相同，说明这个是消耗过的output
					if bytes.Equal(input.PubKey, address) {
						spentOutputs[string(input.TXid)] = append(spentOutputs[string(input.TXid)], input.Index)
					}
				}
			} else {
				fmt.Printf("这是挖矿交易，不做input遍历！")
			}
		}
	}
	return utxos
}

//创建一个函数识别是否为挖矿交易
func (tx *Transaction) IsCoinbase() bool {
	//判断是否为挖矿交易
	//1、交易input只有一个
	//2、交易id为空
	//3、交易的index为-1
	//if len(tx.TXInputs[0].TXid) == 0 && tx.TXInputs[0].Index == -1 && len(tx.TXInputs) == 1{
	if bytes.Equal(tx.TXInputs[0].TXid, []byte{}) && tx.TXInputs[0].Index == -1 && len(tx.TXInputs) == 1 {
		return true
	}
	return false
}

//获取余额
func (c *Cli) GetBalance(address string) {
	//1、校验地址，稍后在做
	if !IsValidAddress(address) {
		fmt.Printf("地址无效！：%s\n", address)
	}
	//2、生成公钥哈希
	pubKeyHash := GetPubKeyFromAddress(address)

	utxo := c.bc.FindUtxo(pubKeyHash)
	//余额
	total := 0.0
	for _, ut := range utxo {
		total += ut.Value
	}
	fmt.Printf("%s的余额为%f", address, total)
}

//校验地址
func IsValidAddress(address string) bool {
	//1、解码
	addressByte := base58.Decode(address)
	if len(addressByte) < 4 {
		fmt.Printf("地址无效！\n")
		return false
	}
	//2、取数据
	payload := addressByte[:len(addressByte)-4]
	checksum1 := addressByte[len(addressByte)-4:]

	//3、做checksum函数
	checksum2 := CheckSum(payload)

	fmt.Printf("checksum1 : %s\n", checksum1)
	fmt.Printf("checksum2 : %s\n", checksum2)

	//4、比较
	return bytes.Equal(checksum2, checksum1)
}

//创建普通交易
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {

	//创建交易之后要进行签名-》所以需要私钥，打开钱包“NewWallets（）”
	ws := NewWallets()
	//找到自己的钱包，根据地址返回自己的wallet
	wallet := ws.WalletsMap[from]
	if wallet == nil {
		fmt.Printf("没有找到该地址的钱包，交易创建失败！\n")
		return nil
	}
	//得到对应的公钥。私钥
	pubKey := wallet.PubKey
	privateKey := wallet.Private

	pubKeyHash := HashPubKey(pubKey)

	//1、找到最合理UTXO集合，map[string][]uint64
	utxos, resValue := bc.FindNeedUTXOs(pubKeyHash, amount)
	if resValue < amount {
		fmt.Printf("余额不足，交易失败！")
		return nil
	}

	var inputs []TXInput
	var outputs []TXOutput

	//2、将这些UTXO逐一转成inputs
	for id, indexArray := range utxos {
		for _, i := range indexArray {
			input := TXInput{[]byte(id), int64(i), nil, pubKey}
			inputs = append(inputs, input)
		}
	}

	//创建交易输出
	output := TXOutput{amount, pubKeyHash}
	outputs = append(outputs, output)

	//找零
	if resValue > amount {
		outputs = append(outputs, TXOutput{resValue - amount, pubKeyHash})
	}
	tx := Transaction{[]byte{}, inputs, outputs}
	tx.SetHash()

	bc.SignTransaction(&tx, privateKey)

	return &tx
}

//签名的具体实现。
//	参数为：私钥，inputs里面所有引用的交易的结构map[string]Transaction
//	map[2222]Transaction222
//	map[3333]Transaction333
func (tx *Transaction) Sign(privateKey *ecdsa.PrivateKey, prevTXs map[string]Transaction) {
	//挖矿区块不需要校验
	if tx.IsCoinbase() {
		return
	}

	//具体签名的动作先不管，稍后继续
	//1、创建一个当前交易的副本：txCopy，使用函数：TrimmedCopy：要把Signature和PubKey字段设置为nil
	//2、循环遍历txCopy的inputs，得到这个input索引的output的公钥哈希

	txCopy := tx.TrimmedCopy()

	for i, input := range txCopy.TXInputs {
		prevTX := prevTXs[string(input.TXid)]
		if len(prevTX.TXID) == 0 {
			log.Panic("引用的交易无效！")
		}
		//不要对input进行赋值，这是一个副本，要对txCopy.TXInputs[xx]进行操作，否则无法把pubKeyHash传进来
		txCopy.TXInputs[i].PubKey = prevTX.TXOutputs[input.Index].PubKeyHash

		//所需要的三个数据都具备了，开始做哈希处理
		//3、生成要签名的数据，要签名的数据一定是哈希值
		//	a、我们要对每一个input都要签名一次，签名的数据是由当前input引用的output的哈希+当前的outputs（都承载在当前这个txCopy里面）
		//	b、要对这个拼好的txCopy进行哈希处理，SetHash得到TXID，这个TXID就是我们要签名最终数据
		txCopy.SetHash()
		//还原，以免影响后面input的签名
		txCopy.TXInputs[i].PubKey = nil
		signDataHash := txCopy.TXID
		//4、执行签名动作得到r、s字节流
		r, s, err := ecdsa.Sign(rand.Reader, privateKey, signDataHash)
		if err != nil {
			log.Panic(err)
		}
		//5、放到我们所签名的input的Signature中
		signature := append(r.Bytes(), s.Bytes()...)
		tx.TXInputs[i].Signature = signature
	}

}

func (tx *Transaction) TrimmedCopy() Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	for _, input := range tx.TXInputs {
		inputs = append(inputs, TXInput{input.TXid, input.Index, nil, nil})
	}
	for _, output := range tx.TXOutputs {
		outputs = append(outputs, output)
	}
	return Transaction{tx.TXID, inputs, outputs}
}

func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
	//是否为挖矿区块
	if tx.IsCoinbase() {
		return true
	}
	//1、得到签名的数据
	txCopy := tx.TrimmedCopy()

	for i, input := range tx.TXInputs {
		prevTX := prevTXs[string(input.TXid)]
		if len(prevTX.TXID) == 0 {
			log.Panic("引用的交易无效！")
		}
		txCopy.TXInputs[i].PubKey = prevTX.TXOutputs[input.Index].PubKeyHash
		txCopy.SetHash()
		dataHash := txCopy.TXID
		//2、得到Signature，反推回r,s
		signature := input.Signature //拆,r,s
		//3、拆解PubKey，X，Y得到原生公钥
		pubKey := input.PubKey //拆,X,Y

		//	1、定义两个辅助的big.int
		r := big.Int{}
		s := big.Int{}

		//	2、拆分我们signature，平均分，前半部分给r，后半部分给s
		r.SetBytes(signature[:len(signature)/2])
		s.SetBytes(signature[len(signature)/2:])

		//a、定义两个辅助的big.int
		X := big.Int{}
		Y := big.Int{}
		//b、pubKey，平均分，前半部分给X，后半部分给Y
		X.SetBytes(pubKey[:len(signature)/2])
		Y.SetBytes(pubKey[len(signature)/2:])
		pubKeyOrigin := ecdsa.PublicKey{elliptic.P256(), &X, &Y}

		//4、Verify
		if !ecdsa.Verify(&pubKeyOrigin, dataHash, &r, &s) {
			return false
		}
	}
	return true
}

//普通交易寻找合理的UTXO
func (bc *BlockChain) FindNeedUTXOs(senderPubKeyHash []byte, amount float64) (map[string][]uint64, float64) {
	//找到的合理的utxos集合
	var utxos map[string][]uint64
	//找到的utxos里面包含钱的总数
	var calc float64
	spentOutputs := make(map[string][]int64)

	//--------

	//创建迭代器
	it := bc.NewIterator()

	for {
		//1、遍历区块
		block := it.Next()
		//遍历交易
		for _, tx := range block.Transactions {
			fmt.Printf("current txid: %x\n", tx.TXID)
			//3、遍历output，找到和自己相关的utxo（在添加output之前检查一下是否已经消耗过）
		output:
			for i, output := range tx.TXOutputs {
				fmt.Printf("current index: %d\n", i)
				//在这里做一个过滤，将所有消耗过得output和当前得所有即将添加output对比一下
				//如果相同，则跳过，否则添加
				//如果当前得交易id存在于我们表示的map，那么说明这个交易里面有消耗过的output

				if spentOutputs[string(tx.TXID)] != nil {
					for _, j := range spentOutputs[string(tx.TXID)] {
						if int64(i) == j {
							//当前准备添加output已经消耗过了，不要再加了
							continue output
						}
					}
				}

				//这个output和我们目标的地址相同，满足条件，加到返回UTXO数组中。
				if bytes.Equal(output.PubKeyHash, senderPubKeyHash) {
					//utxos = append(utxos,output)
					//fmt.Printf("333333:%f\n",utxos[0].Value)
					//比较一下是否满足转账需求
					//a、满足的话，直接返回false，utxos，calc
					//b、不满足继续统计
					if calc < amount {
						//1、把utxo加进来
						utxos[string(tx.TXID)] = append(utxos[string(tx.TXID)], uint64(i))
						//2、统计一下当前utxo的总额
						calc += output.Value

						//加完之后满足条件
						if calc >= amount {
							return utxos, calc
						}
					}

				} else {
					fmt.Printf("不满足转账金额，当前总额：%f\n", calc, amount)
				}
			}
			//如果是挖矿交易，直接跳过
			if !tx.IsCoinbase() {
				//4、遍历input，找到自己花费过的utxo的集合（把自己的消耗过的标示出来）
				//我们定义一个map来保存消费过的output，key是这个output的交易id，value是
				for _, input := range tx.TXInputs {
					//判断一下当前这个input和目标是否一致，如果相同，说明这个是消耗过的output
					//if input.PubKey == senderPubKeyHash {
					pubKeyHash := HashPubKey(input.PubKey)
					if bytes.Equal(pubKeyHash, senderPubKeyHash) {
						spentOutputs[string(input.TXid)] = append(spentOutputs[string(input.TXid)], input.Index)
					}
				}
			} else {
				fmt.Printf("这是挖矿交易，不做input遍历！")
			}
		}
	}

	//--------

	return utxos, calc
}

//from转账方，to收款方，amount数额，miner矿工，data数据
func (c *Cli) Send(from, to string, amount float64, miner, data string) {
	//校验地址
	if !IsValidAddress(from) {
		fmt.Printf("地址无效！from：%s\n", from)
	}
	if !IsValidAddress(to) {
		fmt.Printf("地址无效！to：%s\n", to)
	}
	if !IsValidAddress(miner) {
		fmt.Printf("地址无效！miner：%s\n", miner)
	}
	//具体逻辑
	//1、创建挖矿交易
	coinbase := NewCoinBaseTx(miner, data)

	//2、创建普通交易
	tx := NewTransaction(from, to, amount, c.bc)
	if tx == nil {
		fmt.Printf("无效的交易\n")
		return
	}
	//3、添加到区块
	c.bc.AddBlock([]*Transaction{coinbase, tx})
	fmt.Printf("转账成功！")
}

func (c *Cli) NewWallet() {
	//wallet := NewWallet()
	//fmt.Printf("私钥：%v", wallet.Private)
	//fmt.Printf("公钥：%s", wallet.PubKey)
	ws := NewWallets()
	address := ws.CreateWallet()
	fmt.Printf("地址：%s\n", address)
	//for address := range ws.WalletsMap {
	//	fmt.Printf("地址：%s\n", address)
	//}
}

//创建钱包结构
type Wallet struct {
	//私钥
	Private *ecdsa.PrivateKey
	//约定，这里的publickey不存储原始的公钥，而是存储X,Y的拼接，参考r，s
	PubKey []byte
}

//创建钱包
func NewWallet() *Wallet {
	//创建曲线
	curve := elliptic.P256()
	//生成私钥
	private, err := ecdsa.GenerateKey(curve, rand.Reader)
	if err != nil {
		log.Panic()
	}
	//生成公钥
	pubKeyOrig := private.PublicKey
	//拼接X,Y
	pubkey := append(pubKeyOrig.X.Bytes(), pubKeyOrig.Y.Bytes()...)

	return &Wallet{private, pubkey}
}

//生成地址
func (w *Wallet) NewAddress() string {
	pubKey := w.PubKey

	rip160HashValue := HashPubKey(pubKey)

	version := byte(00)

	//拼接version
	payload := append([]byte{version}, rip160HashValue...)

	checkCode := CheckSum(payload)

	//25字节数据
	payload = append(payload, checkCode...)

	//go语言有一个库，叫做btcd，这个是go语言实现的比特币全节点源码
	address := base58.Encode(payload)

	return address
}
func HashPubKey(data []byte) []byte {
	hash := sha256.Sum256(data)

	//理解为编码器
	rip160hasher := ripemd160.New()
	_, err := rip160hasher.Write(hash[:])

	if err != nil {
		log.Panic(err)
	}

	//返回rip160的hash结果
	rip160HashValue := rip160hasher.Sum(nil)
	return rip160HashValue
}
func CheckSum(data []byte) []byte {
	//checksum
	//两次sha256
	hash1 := sha256.Sum256(data)
	hash2 := sha256.Sum256(hash1[:])

	//前四个字节校验码
	checkCode := hash2[:4]
	return checkCode
}

//创建一个所有钱包总的集合
type Wallets struct {
	//map[地址]钱包
	WalletsMap map[string]*Wallet
}

//创建方法
func NewWallets() *Wallets {
	var ws Wallets
	ws.WalletsMap = make(map[string]*Wallet)
	ws.loadFile()
	return &ws
}

//创建钱包
func (ws *Wallets) CreateWallet() string {
	wallet := NewWallet()
	address := wallet.NewAddress()

	ws.WalletsMap[address] = wallet
	ws.saveToFile()
	return address
}

//保存方法，把新建的wallet添加进去
func (ws *Wallets) saveToFile() {
	var buffer bytes.Buffer

	//注册interface类型
	gob.Register(elliptic.P256())

	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(ws)
	if err != nil {
		log.Panic(err)
	}
	ioutil.WriteFile(walletFile, buffer.Bytes(), 0600)
}

//读取文件方法，把所有的wallet读出来
func (ws *Wallets) loadFile() {
	//在读取之前，要先确认文件是否存在，如果不存在，直接退出
	_, err := os.Stat(walletFile)
	if os.IsNotExist(err) {
		//ws.WalletsMap = make(map[string]*Wallet)
		return
	}

	//读取内容
	content, err := ioutil.ReadFile(walletFile)
	if err != nil {
		log.Panic(err)
	}

	//解码
	//注册interface类型
	gob.Register(elliptic.P256())

	decoder := gob.NewDecoder(bytes.NewReader(content))

	var wsLocal Wallets
	err = decoder.Decode(&wsLocal)
	if err != nil {
		log.Panic(err)
	}
	//对于结构体来说，里面有map的，要指定赋值，不要在最外层直接赋值
	ws.WalletsMap = wsLocal.WalletsMap
}

func (c *Cli) ListAllAddress() {
	ws := NewWallets()
	addresses := ws.ListAllAddress()
	for i, address := range addresses {
		fmt.Printf("地址%d：%s\n", i, address)
	}
}

//获取所有钱包的地址
func (ws *Wallets) ListAllAddress() []string {
	var addresses []string
	//遍历钱包，将所有的Key取出来返回
	for address := range ws.WalletsMap {
		addresses = append(addresses, address)
	}
	return addresses
}
func GetPubKeyFromAddress(address string) []byte {
	//1、解码
	//2、截取出公钥哈希：去除version(1字节)，去除校验码（4字节）
	addressByte := base58.Decode(address) //25字节
	len := len(addressByte)

	pubKeyHash := addressByte[1 : len-4]
	return pubKeyHash
}
func main() {
	bc := NewBlockChain("第一人！")
	cli := Cli{bc}
	defer bc.db.Close()
	cli.Run()
	//bc.AddBlock([]byte("111111111"))
	//bc.AddBlock([]byte("5555555555"))
	//for i,block := range bc.block{
	//	fmt.Printf("区块高度：%d\n",i)
	//	fmt.Printf("---Block Prehash:%x\n",block.PreHash)
	//	fmt.Printf("---Block Data:	%s\n",block.Data)
	//	fmt.Printf("---Block Myhash:	%x\n",block.MyHash)
	//}
	//it := bc.NewIterator()
	//for {
	//	block := it.Next()
	//	fmt.Printf("======================================\n")
	//	fmt.Printf("---Block Prehash:%x\n",block.PreHash)
	//	fmt.Printf("---Block Data:	%s\n",block.Data)
	//	fmt.Printf("---Block Myhash:	%x\n",block.MyHash)
	//	if len(block.PreHash) == 0{
	//		fmt.Println("遍历结束！")
	//		break
	//	}
	//}
}
